package logger

import (
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"

	"egin/pkg/config"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var zlog *zap.Logger
var level zapcore.Level

// InitLogger 初始化Logger
func init() {

	gin.SetMode(config.C().App.Mode)

	switch config.C().Log.Level { // 初始化配置文件的Level
	case "debug":
		level = zap.DebugLevel
	case "info":
		level = zap.InfoLevel
	case "warn":
		level = zap.WarnLevel
	case "error":
		level = zap.ErrorLevel
	case "dpanic":
		level = zap.DPanicLevel
	case "panic":
		level = zap.PanicLevel
	case "fatal":
		level = zap.FatalLevel
	default:
		level = zap.InfoLevel
	}

	writeSyncer := getLogWriterByDate(config.C().Log.FileName, config.C().Log.MaxSize)

	encoder := getEncoder(config.C().Log.IsJson)
	core := zapcore.NewCore(encoder, writeSyncer, level)

	if level == zap.DebugLevel || level == zap.ErrorLevel {
		zlog = zap.New(core, zap.AddStacktrace(level))
	} else {
		zlog = zap.New(core)
	}

	zap.ReplaceGlobals(zlog) // 替换zap包中全局的zlog实例，后续在其他包中只需使用zap.L()调用即可

}

func getLogWriterByDate(filename string, maxSize int) zapcore.WriteSyncer {

	fileWriter, _ := rotatelogs.New(
		strings.Replace(filename, ".log", "", -1)+"-%Y%m%d%H.log", // 没有使用go风格反人类的format格式
		rotatelogs.WithLinkName(filename),
		rotatelogs.WithMaxAge(time.Hour*24*time.Duration(maxSize)),
		rotatelogs.WithRotationTime(time.Hour),
	)
	if gin.Mode() == gin.DebugMode {
		return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter))
	}
	return zapcore.AddSync(fileWriter)
}

// getEncoderConfig 获取zapcore.EncoderConfig
func getEncoderConfig(isJSONEncoder bool) (confs zapcore.EncoderConfig) {
	confs = zapcore.EncoderConfig{
		MessageKey:     "message",
		LevelKey:       "level",
		TimeKey:        "time",
		NameKey:        "logger",
		CallerKey:      "caller",
		StacktraceKey:  config.C().Log.Stacktrace,
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalLevelEncoder,
		EncodeTime:     CustomTimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.FullCallerEncoder,
	}

	encodeLevel := config.C().Log.EncodeLevel

	switch {
	case encodeLevel == "LowercaseLevelEncoder" && !isJSONEncoder: // console小写编码器
		confs.EncodeLevel = zapcore.LowercaseLevelEncoder
	case encodeLevel == "LowercaseLevelEncoder" && isJSONEncoder: // json小写编码器
		confs.EncodeLevel = zapcore.LowercaseLevelEncoder
	case encodeLevel == "LowercaseColorLevelEncoder" && !isJSONEncoder: // console小写编码器带颜色
		confs.EncodeLevel = zapcore.LowercaseColorLevelEncoder
	case encodeLevel == "LowercaseColorLevelEncoder" && isJSONEncoder: // json小写编码器带颜色
		confs.EncodeLevel = zapcore.LowercaseColorLevelEncoder
	case encodeLevel == "CapitalLevelEncoder" && !isJSONEncoder: // console大写编码器
		confs.EncodeLevel = zapcore.CapitalLevelEncoder
	case encodeLevel == "CapitalLevelEncoder" && isJSONEncoder: // json大写编码器
		confs.EncodeLevel = zapcore.CapitalLevelEncoder
	case encodeLevel == "CapitalColorLevelEncoder" && !isJSONEncoder: // console 大写编码器带颜色
		confs.EncodeLevel = zapcore.CapitalColorLevelEncoder
	case encodeLevel == "CapitalColorLevelEncoder" && isJSONEncoder: // json 大写编码器带颜色
		confs.EncodeLevel = zapcore.CapitalColorLevelEncoder
	default:
		confs.EncodeLevel = zapcore.LowercaseLevelEncoder
	}
	return confs
}

// getEncoder 获取zapcore.Encoder
func getEncoder(isJSONEncoder bool) zapcore.Encoder {

	if isJSONEncoder {
		return zapcore.NewJSONEncoder(getEncoderConfig(isJSONEncoder))
	}
	return zapcore.NewConsoleEncoder(getEncoderConfig(isJSONEncoder))
}

// 自定义日志输出时间格式
func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {

	if config.C().Log.IsJson {
		enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
	} else {
		enc.AppendString(t.Format(config.C().Log.Prefix + " 2006/01/02 - 15:04:05.000"))
	}

}

// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		c.Next()

		cost := time.Since(start)
		zlog.Info(path,
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("query", query),
			zap.String("ip", c.ClientIP()),
			//zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost),
		)
	}
}

func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					zlog.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					_ = c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					zlog.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					zlog.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}
